package com.ficus.road.maintain.service.coverage;

import com.ficus.road.maintain.core.model.GpsHistory;
import com.ficus.road.maintain.core.model.RoadInfo;
import com.ficus.road.maintain.core.model.TheoryGps;
import com.ficus.road.maintain.core.model.config.GlobalConfig;
import com.ficus.road.maintain.core.model.coverage.RoadCoverageYT;
import com.ficus.road.maintain.core.model.coverage.UnitCoverageWeek;
import com.ficus.road.maintain.dao.GlobalConfigDao;
import com.ficus.road.maintain.dao.GpsHistoryDao;
import com.ficus.road.maintain.dao.RoadInfoDao;
import com.ficus.road.maintain.dao.coverage.RoadCoverageYTDao;
import com.ficus.road.maintain.dao.coverage.UnitCoverageWeekDao;
import com.ficus.road.maintain.service.CycleNumService;
import com.ficus.road.maintain.service.TheoryGpsService;
import com.ficus.road.maintain.util.MyDateUtil;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.stream.Collectors;

@Component
@Slf4j
@RequiredArgsConstructor
public class YtCoverageTask {
    public static final String COVERAGE_TIMESTAMP = "coverage_timestamp";
    public static final double COVERAGE_THRESHOLD = 0.85;
    private final RoadInfoDao roadInfoDao;
    private final RoadCoverageYTDao roadCoverageYTDao;
    private final CycleNumService cycleNumService;
    private final GlobalConfigDao globalConfigDao;
    private final GpsHistoryDao gpsHistoryDao;
    private final TheoryGpsService theoryGpsService;
    private final UnitCoverageWeekDao unitCoverageWeekDao;
    //roadId,上个点的情况
    private final Map<String, Pair<TheoryGps, Date>> lastRoadIdInfo = Maps.newConcurrentMap();

    /**
     * 每天凌晨生成所有道路的默认覆盖率
     */
    @Scheduled(cron = "0 30 0 * * ?", zone = "GMT+8:00")
    private void initToday() {
        String todayStr = MyDateUtil.dateFormatStr(new Date(), "yyyy-MM-dd");
        initCoverageRoadCoverageByDay(todayStr);
    }

    public void initCoverageRoadCoverageByDay(String todayStr) {
        String currentCycleNum = cycleNumService.getByDate(todayStr).getCycleNum();
        log.info("initCoverageRoadCoverageToday begin,today={},currentCycleNum={}", todayStr, currentCycleNum);
        Map<String, List<TheoryGps>> roadTheoryGpsMap = theoryGpsService.findAll().stream().collect(Collectors.groupingBy(TheoryGps::getRoadInfoId));
        List<RoadInfo> roadInfoList = roadInfoDao.findByQuery(new Query(Criteria.where("coordinates").exists(true).ne(null)
                .and("unit_id").exists(true).ne(null)));
        roadInfoList.forEach(roadInfo -> {
            RoadCoverageYT roadCoverageYT = new RoadCoverageYT();
            roadCoverageYT.setRoadSecNum(roadInfo.getSecNum());
            roadCoverageYT.setRoadId(roadInfo.getId());
            roadCoverageYT.setUnitId(roadInfo.getUnitId());
            roadCoverageYT.setCycleNum(currentCycleNum);
            roadCoverageYT.setDay(todayStr);
            roadCoverageYT.setCoverageLocationIndex(Sets.newConcurrentHashSet());
            roadCoverageYT.setIndexLength(roadTheoryGpsMap.getOrDefault(roadInfo.getId(), Lists.newArrayList()).size());
            if (roadInfo.getStreetType() != null && StringUtils.equals("WTO_WAY", roadInfo.getStreetType())) {
                roadCoverageYT.setId(roadInfo.getId() + todayStr + RoadCoverageYT.RoadDirectionEnum.UP);
                roadCoverageYT.setDirection(RoadCoverageYT.RoadDirectionEnum.UP);
                roadCoverageYTDao.insert(roadCoverageYT);
                roadCoverageYT.setDirection(RoadCoverageYT.RoadDirectionEnum.DOWN);
                roadCoverageYT.setId(roadInfo.getId() + todayStr + RoadCoverageYT.RoadDirectionEnum.DOWN);
                roadCoverageYTDao.insert(roadCoverageYT);
            } else {
                roadCoverageYT.setDirection(RoadCoverageYT.RoadDirectionEnum.NONE);
                roadCoverageYT.setId(roadInfo.getId() + todayStr + RoadCoverageYT.RoadDirectionEnum.NONE);
                roadCoverageYTDao.insert(roadCoverageYT);
            }
        });

        log.info("initCoverageRoadCoverageToday end,today={},currentCycleNum={}", todayStr, currentCycleNum);
    }

    /**
     * 覆盖率计算,按照道路分为上行和下行
     */
    @SuppressWarnings("rawtypes")
    @Scheduled(fixedDelay = 60000, initialDelay = 1)
    public void calculateCoverage() {
        GlobalConfig one = globalConfigDao.findOne(new Query(Criteria.where("config_name").is(COVERAGE_TIMESTAMP)));
        if (Objects.isNull(one)) {
            one = new GlobalConfig();
            one.setConfigValue(new Date().getTime());
            one.setConfigName(COVERAGE_TIMESTAMP);
            globalConfigDao.insert(one);
            return;
        }
        Date lastTimestamp = new Date((Long) one.getConfigValue());
        Map<String, RoadCoverageYT> currentCycleResult = Maps.newHashMap();
        Map<String, RoadInfo> roadInfoMap = roadInfoDao.findAll().stream().collect(Collectors.toMap(RoadInfo::getId, v -> v));
        while (true) {
            List<GpsHistory> histories = gpsHistoryDao.findByQuery(new Query(Criteria.where("is_fixed").is(Boolean.TRUE)
                    .and("creation_time").gte(lastTimestamp)).with(Sort.by(Sort.Direction.ASC, "creation_time"))
                    .limit(10000));
            if (CollectionUtils.isEmpty(histories)) {
                return;
            }

            for (GpsHistory history : histories) {
                lastTimestamp = history.getCreationTime();
                one.setConfigValue(lastTimestamp.getTime());
                List<TheoryGps> theoryGps = theoryGpsService.geoSearchList(history);
                if (CollectionUtils.isEmpty(theoryGps)) {
                    continue;
                }
                for (TheoryGps currentTheoryGps : theoryGps) {
                    if (Objects.isNull(currentTheoryGps)) {
                        continue;
                    }
                    String roadInfoId = currentTheoryGps.getRoadInfoId();
                    Pair<TheoryGps, Date> theoryGpsDatePair = lastRoadIdInfo.get(roadInfoId);
                    RoadInfo roadInfo = roadInfoMap.get(roadInfoId);
                    lastRoadIdInfo.put(roadInfoId, Pair.of(currentTheoryGps, history.getCreationTime()));
                    if (Objects.isNull(theoryGpsDatePair)) {
                        continue;
                    }
                    Date lastGpsDate = theoryGpsDatePair.getRight();
                    //不是同一天的数据,保存历史并重新开始计算
                    if (!DateUtils.isSameDay(lastGpsDate, history.getCreationTime())) {
                        if (!StringUtils.equals("WTO_WAY", roadInfo.getStreetType())) {
                            String lastDayStr = MyDateUtil.dateFormatStr(lastGpsDate, "yyyy-MM-dd");
                            String coverageRecordId = roadInfoId + lastDayStr + RoadCoverageYT.RoadDirectionEnum.NONE;
                            RoadCoverageYT coverageYTDaoOne = currentCycleResult.get(coverageRecordId);
                            if (Objects.nonNull(coverageYTDaoOne)) {
                                roadCoverageYTDao.saveOrUpdate(coverageYTDaoOne);
                            }
                        }
                        continue;
                    }
                    TheoryGps lastTheoryGps = theoryGpsDatePair.getLeft();
                    if (Objects.equals(lastTheoryGps.getSequence(), currentTheoryGps.getSequence())) {
                        //同一个桩号
                        continue;
                    }
                    RoadCoverageYT.RoadDirectionEnum directionEnum;
                    String currentDayStr = MyDateUtil.dateFormatStr(history.getCreationTime(), "yyyy-MM-dd");

                    if (StringUtils.equals("WTO_WAY", roadInfo.getStreetType())) {
                        if (currentTheoryGps.getSequence() > lastTheoryGps.getSequence()) {
                            //上行
                            directionEnum = RoadCoverageYT.RoadDirectionEnum.UP;
                        } else {
                            directionEnum = RoadCoverageYT.RoadDirectionEnum.DOWN;
                        }
                    } else {
                        directionEnum = RoadCoverageYT.RoadDirectionEnum.NONE;
                    }
                    String coverageRecordId = roadInfoId + currentDayStr + directionEnum;
                    RoadCoverageYT coverageYTDaoOne = currentCycleResult.get(coverageRecordId);
                    if (Objects.isNull(coverageYTDaoOne)) {
                        coverageYTDaoOne = roadCoverageYTDao.fetch(coverageRecordId);
                    }

                    if (Objects.isNull(coverageYTDaoOne)) {
                        continue;
                    }
                    Set<Integer> coverageLocationIndex = coverageYTDaoOne.getCoverageLocationIndex();
                    if (coverageLocationIndex.contains(currentTheoryGps.getSequence()) && coverageLocationIndex.contains(lastTheoryGps.getSequence())) {
                        continue;
                    }

                    coverageLocationIndex.add(currentTheoryGps.getSequence());
                    coverageLocationIndex.add(lastTheoryGps.getSequence());
                    if (Math.abs(currentTheoryGps.getSequence() - lastTheoryGps.getSequence()) == 1) {
                        coverageLocationIndex.add((lastTheoryGps.getSequence() + currentTheoryGps.getSequence()) / 2);
                    }
                    coverageYTDaoOne.setCoverageLocationIndex(coverageLocationIndex);
                    if (coverageYTDaoOne.getIndexLength() != null && coverageYTDaoOne.getIndexLength() > 0) {
                        double coverage = (double) coverageYTDaoOne.getCoverageLocationIndex().size() / coverageYTDaoOne.getIndexLength();
                        if (coverage >= COVERAGE_THRESHOLD) {
                            coverage = 1;
                            for (int i = 0; i < coverageYTDaoOne.getIndexLength(); i++) {
                                coverageLocationIndex.add(i);
                            }
                            coverageYTDaoOne.setCoverageLocationIndex(coverageLocationIndex);
                        }
                        coverageYTDaoOne.setCycleCoverage(coverage);
                    }
                    currentCycleResult.put(coverageRecordId, coverageYTDaoOne);
                }

            }
            currentCycleResult.values().forEach(roadCoverageYTDao::saveOrUpdate);

            globalConfigDao.saveOrUpdate(one);
            if (histories.size() < 10000) {
                return;
            }
        }
    }


    //    @Scheduled(cron = "0 0/10 6-22 * * ?", zone = "GMT+8:00")
    public void refreshUnitCoverageWeek() {
        String currentCycleNum = cycleNumService.getCurrentCycleNum();
        List<RoadCoverageYT> byQuery = roadCoverageYTDao.findByQuery(new Query(Criteria.where("cycle_num").is(currentCycleNum)));
        if (CollectionUtils.isEmpty(byQuery)) {
            return;
        }
        Map<String, List<RoadCoverageYT>> unitCoverageMap = byQuery.stream().collect(Collectors.groupingBy(RoadCoverageYT::getUnitId));
        List<RoadInfo> roadInfoList = roadInfoDao.findByQuery(new Query(Criteria.where("coordinates").exists(true).ne(null)));
        Map<String, List<RoadInfo>> unitRoadMap = roadInfoList.stream().collect(Collectors.groupingBy(RoadInfo::getUnitId));
        unitCoverageMap.forEach((unitId, coverageList) -> {
            int shouldCoverage = unitRoadMap.get(unitId).stream().mapToInt(r -> r.getCoordinates().size()).sum();
            int realCoverage = coverageList.stream()
                    .map(RoadCoverageYT::getCoverageLocationIndex)
                    .flatMap(Collection::stream)
                    .collect(Collectors.toSet())
                    .size();
            UnitCoverageWeek unitCoverageDay = UnitCoverageWeek.builder()
                    .id(unitId + currentCycleNum)
                    .cycleNum(currentCycleNum)
                    .unitId(unitId)
                    .shouldCover(shouldCoverage)
                    .realCover(realCoverage)
                    .cycleCoverage((double) realCoverage / shouldCoverage)
                    .lastModificationTime(new Date())
                    .build();
            unitCoverageWeekDao.saveOrUpdate(unitCoverageDay);
        });
    }
}
